using System;
using System.Collections;
using System.Collections.Generic;
using NFreeMarker.Ext.Util;
using NFreeMarker.Template;
using NFreeMarker.Template.Utility;

namespace NFreeMarker.Ext.Beans
{
    /**
     * <p>A special case of {@link BeanModel} that adds implementation
     * for {@link TemplateMethodModelEx} on map objects that is a shortcut for the
     * <tt>Map.get()</tt> method. Note that if the passed argument itself is a
     * reflection-wrapper model, then the map lookup will be performed using the
     * wrapped object as the key. Note that you can call <tt>get()</tt> using the
     * <tt>map.key</tt> syntax inherited from {@link BeanModel} as well, 
     * however in that case the key is always a string.</p>
     * <p>The class itself does not implement the {@link freemarker.template.TemplateCollectionModel}.
     * You can, however use <tt>map.entrySet()</tt>, <tt>map.keySet()</tt>, or
     * <tt>map.values()</tt> to obtain {@link freemarker.template.TemplateCollectionModel} instances for 
     * various aspects of the map.</p>
     * @author Attila Szegedi
     * @version $Id: MapModel.java,v 1.26.2.3 2006/02/26 18:26:37 revusky Exp $
     */
    public class MapModel : StringModel, ITemplateMethodModelEx
    {
        internal static readonly IModelFactory Factory = new ModelFactoryImpl();

        private class ModelFactoryImpl : IModelFactory
        {
            public ITemplateModel Create(object obj, IObjectWrapper wrapper)
            {
                return new MapModel((IDictionary) obj, (BeansWrapper) wrapper);
            }
        }

        /**
         * Creates a new model that wraps the specified map object.
         * @param map the map object to wrap into a model.
         * @param wrapper the {@link BeansWrapper} associated with this model.
         * Every model has to have an associated {@link BeansWrapper} instance. The
         * model gains many attributes from its wrapper, including the caching 
         * behavior, method exposure level, method-over-item shadowing policy etc.
         */
        public MapModel(IDictionary map, BeansWrapper wrapper)
            : base(map, wrapper)
        {
        }

        /**
         * The first argument is used as a key to call the map's <tt>get</tt> method.
         */
        public object Exec(IList<ITemplateModel> arguments)
        {
            object key = Unwrap(arguments[0]);
            return Wrap(((IDictionary) Object).GetValueOrDefault(key));
        }

        public object Exec(IList<string> arguments)
        {
            throw new NotSupportedException();
        }

        /**
         * Overridden to invoke the generic get method by casting to Map instead of 
         * through reflection - should yield better performance.
         */
        protected ITemplateModel InvokeGenericGet(IDictionary keyMap, Type type, string key)
        {
            var map = (IDictionary) Object;
            object val = map.GetValueOrDefault(key);
            if (val == null)
            {
                if (key.Length == 1)
                {
                    // just check for Character key if this is a single-character string
                    char charKey = key[0];
                    val = map.GetValueOrDefault(charKey);
                    if (val == null && !(map.Contains(key) || map.Contains(charKey)))
                    {
                        return Unknown;
                    }
                }
                else if (!map.Contains(key))
                {
                    return Unknown;
                }
            }
            return Wrap(val);
        }

        public override bool IsEmpty()
        {
            return ((IDictionary) Object).Count == 0 && base.IsEmpty();
        }

        public override int Size()
        {
            return KeySet().Count;
        }

        protected override HashSet<object> KeySet()
        {
            HashSet<object> set = base.KeySet();
            foreach (object key in ((IDictionary)Object).Keys)
            {
                set.Add(key);
            }
            return set;
        }
    }
}